/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.command;

import java.util.Calendar;
import java.util.Comparator;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;

import jp.aist.gtrc.plus.command.util.PlusReserveCommand;
import jp.aist.gtrc.plus.command.util.PlusReserveStatusOptions;
import jp.aist.gtrc.plus.reserve.ReserveId;
import jp.aist.gtrc.plus.reserve.ReserveServer;
import jp.aist.gtrc.plus.reserve.ReserveStatus;
import jp.aist.gtrc.plus.scheduler.util.TextUtil;


public class PlusReserveStatus  extends PlusReserveCommand{

	interface ShowStatusFormatter {
		void showReserveStatus(ReserveStatus status);
		void showReserveStatusAll(ReserveStatus[] statusArray);
		void showReserveStatusFull(ReserveStatus status);
	}

	static class HumanReadableFormatter implements ShowStatusFormatter {

		private String calendarToStr(Calendar c) {
			// "Mmm DD HH:MM" (ex: "Jan 01 12:34")
			return String.format(Locale.US, "%1$tb %1$td %1$tR", c);
		}

		private String diffToStr(Calendar start, Calendar end) {
			long diff = (end.getTimeInMillis() - start.getTimeInMillis())/1000;
			// "HHHHhMMm" (ex: "   1h00m", "  12h34m")
			return String.format(Locale.US, "%4dh%02dm",
					diff/3600, (diff%3600)/60);
		}

		private void showParams(String id, String owner, String start,
				String end, String duration, String state) {
			System.out.format(Locale.US, "%8s %-10s %12s  %12s %8s %s%n",
					id, owner, start, end, duration, state);
		}

		public void showReserveStatus(ReserveStatus status) {

			Calendar start = status.getStartTime();
			Calendar end = status.getEndTime();
			showParams(
					status.getReserveId().toString(),
					status.getOwner(),
					calendarToStr(start),
					calendarToStr(end),
					diffToStr(start, end),
					status.getStateStr());

		}

		protected void showTableHeader() {

			showParams("id", "owner", "start    ", "end    ", "duration", "state");

		}

		public void showReserveStatusAll(ReserveStatus[] statusArray){

			showTableHeader();
			for (ReserveStatus status : statusArray) {
				showReserveStatus(status);
			}

		}

		private void showOneParam(String key, String value) {
			System.out.println(key + "\t" + value);
		}

		public void showReserveStatusFull(ReserveStatus status) {
			showOneParam("id", status.getReserveId().toString());
			showOneParam("owner", status.getOwner());
			showOneParam("users", TextUtil.fromArray(status.getUsers(), " "));
			showOneParam("start", String.format(Locale.US, "%tc", status.getStartTime()));
			showOneParam("end", String.format(Locale.US, "%tc", status.getEndTime()));
			showOneParam("state", status.getStateStr());
			showOneParam("nodes", TextUtil.fromArray(status.getReservedNodeNames(), " "));
			showOneParam("options", status.getOptionsInLine());
		}

	}

	static class SortByNodeFormatter extends HumanReadableFormatter {

		private static Comparator<ReserveStatus> compStatus
			= new Comparator<ReserveStatus>() {
				public int compare(ReserveStatus o1, ReserveStatus o2) {
					int comp = o1.getStartTime().compareTo(o2.getStartTime());
					if (comp != 0) return comp;
					comp = o1.getEndTime().compareTo(o2.getEndTime());
					if (comp != 0) return comp;
					return o1.getReserveId().compareTo(o2.getReserveId());
				}
			};

		private void showEntries(String hostname, TreeSet<ReserveStatus> reserves) {

			System.out.println(hostname);
			for (ReserveStatus status : reserves) {
				showReserveStatus(status);
			}

		}

		public void showReserveStatusAll(ReserveStatus[] statusArray) {

			showTableHeader();

			TreeMap<String, TreeSet<ReserveStatus>> map
				= new TreeMap<String, TreeSet<ReserveStatus>>();
			TreeSet<ReserveStatus> hostSet;
			TreeSet<ReserveStatus> unusableSet = new TreeSet<ReserveStatus>(compStatus);

			for (ReserveStatus status : statusArray) {
				switch (status.getReserveState()) {
				case Confirmed:
				case Running:
					for (String hostname : status.getReservedNodeNames()) {
						hostSet = map.get(hostname);
						if (hostSet == null) {
							hostSet = new TreeSet<ReserveStatus>(compStatus);
							map.put(hostname, hostSet);
						}
						hostSet.add(status);
					}
					break;
				default:
					unusableSet.add(status);
					break;
				}
			}

			for (Map.Entry<String, TreeSet<ReserveStatus>> set : map.entrySet()) {
				showEntries(set.getKey(), set.getValue());
			}
			if (unusableSet.size() > 0) {
				showEntries("UNUSABLE", unusableSet);
			}

		}

	}
	static class XMLFormatter implements ShowStatusFormatter {

		private static final String RSV_TAG = "reservation";
		private static final String USERS_TAG = "users";
		private static final String NODES_TAG = "nodes";
		private static final String RSV_LIST_TAG = "reservationList";

		private String calendarToISO8601(Calendar c) {
			// "yyyy-mm-ddThh:mm:ss{+-}zz:zz"
			// ex "2006-01-23T11:23:45+09:00"
			// Note: Java's TimeZone format is "zzzz" (RFC822),
			// not "zz:zz". We must insert ":".
			// tz will be "{+-}zzzz", timeZone will be "{+-}zz:zz"
			String tz = String.format(Locale.US, "%tz", c);
			String timeZone = tz.substring(0, 3) + ":" + tz.substring(3);
			String time = String.format(Locale.US,	"%1$tFT%1$tT", c);
			return time + timeZone;
		}

		private void println(String text) {
			System.out.println(text);
		}
		private void showStartTag(String tag) {
			println("<" + tag + ">");
		}
		private void showEndTag(String tag) {
			println("</" + tag + ">");
		}
		private String makeElement(String tag, String value) {
			return "<" + tag + ">" + value + "</" + tag + ">";
		}
		private void showElement(String tag, String value) {
			System.out.println(makeElement(tag, value));
		}

		public void showReserveStatus(ReserveStatus status) {
			showReserveStatusFull(status);
		}

		public void showReserveStatusAll(ReserveStatus[] statusArray) {

			showStartTag(RSV_LIST_TAG);
			for (ReserveStatus status : statusArray) {
				showReserveStatus(status);
			}
			showEndTag(RSV_LIST_TAG);

		}

		private void showAttrs(String tag, String attrName, String[] attrs) {
			showStartTag(tag);
			for (String value : attrs) {
				showElement(attrName, value);
			}
			showEndTag(tag);

		}
		public void showReserveStatusFull(ReserveStatus status) {

			showStartTag(RSV_TAG);

			showElement("id", status.getReserveId().toString());
			showElement("owner", status.getOwner());
			showAttrs(USERS_TAG, "user", status.getUsers());
			showElement("start", calendarToISO8601(status.getStartTime()));
			showElement("end", calendarToISO8601(status.getEndTime()));
			showElement("state", status.getStateStr());
			showElement("numNodes", Integer.toString(status.getNodeNum()));
			showAttrs(NODES_TAG, "node", status.getReservedNodeNames());

			showEndTag(RSV_TAG);

		}
	}

	private static ShowStatusFormatter makeFormatter(PlusReserveStatusOptions options) {

		if (options.isShowByXML())
			return new XMLFormatter();
		if (options.isShowSortedByNode())
			return new SortByNodeFormatter();
		return new HumanReadableFormatter();

	}

	/*
	 * To Show All of Reserve Status (without reserved node information)
	 * 	java PlusReserveStatus {-x}
	 *
	 * To Show one Reserve Status (with reserved node information)
	 * 	java PlusReserveStatus -r rsvId {-x}
	 *
	 * Show in XML format if "-x" is specified.
	 *
	 * To Show command help:
	 * 	java PlusReserve -h
	 */
	public static void main(String[] args){

		PlusReserveStatusOptions options = new PlusReserveStatusOptions("plus_status");
		ShowStatusFormatter formatter = null;
		ReserveServer server = null;
		ReserveId rsvId = null;
		String owner = null;
		Calendar start = null;
		Calendar end = null;

		try {
			options.parse(args);
			formatter = makeFormatter(options);
			rsvId = options.getReserveId();
			owner = options.getOwner();
			start = options.getStartTime();
			end = options.getEndTime();
		} catch (Exception e) {
			showCommandHelp(options, e);
			System.exit(1);
		}

		try {
			server = getReserveServer(options);
			if (rsvId != null) {
				// show one reserve status
				formatter.showReserveStatusFull(server.getStatus(rsvId));
			} else {
				int nShowExpired = options.getNumShowExpired();
				formatter.showReserveStatusAll(
						server.getStatus(owner, start, end, nShowExpired));
			}
		} catch (Exception e) {
			showException(e);
			System.exit(1);
		}

	}

}
